﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace MyRaceGame.GameLogic
{
    public class RaceController
    {
        public RaceController(int laps)
        {
            this.laps = laps;
        }

        public void AddRacer(RacerCharacter racer)
        {
            racers.Add(racer);
        }

        public void RemoveRacer(RacerCharacter racer)
        {
            racers.Remove(racer);
        }

        public void RemoveAllExceptHumanRacer()
        {
            racers.RemoveAll(isNotHumanRacer);
        }

        bool isNotHumanRacer(RacerCharacter racer)
        {
            return !racer.IsHuman;
        }

        public void InitializeRacers()
        {
            foreach (RacerCharacter racer in racers)
            {
                RacerAnimationPlayer rap = (RacerAnimationPlayer)racer.AnimationPlayer;
                rap.CurrentAnimation = rap.IdleAnimation;
            }
        }

        public void UpdateRacers(GameTime gameTime)
        {
            if (!RaceFinished)
                racers.Sort();

            else if (racers.Count != 0 && HasFinishedRace(racers[0]) && !FreeRace)
            {
                finishRacers.Add((TimeCountedRacer)racers[0]);
                racers.Remove(racers[0]);
            }

            foreach (RacerCharacter racer in racers)
                racer.Update(gameTime);

            foreach (RacerCharacter racer in finishRacers)
                racer.Update(gameTime);
        }

        public void DrawRacers(Matrix view, Matrix projection, Vector3 cameraPosition)
        {
            foreach (RacerCharacter racer in racers)
                racer.Draw(view, projection, cameraPosition);

            foreach (RacerCharacter racer in finishRacers)
                racer.Draw(view, projection, cameraPosition);
        }

        /// <summary>
        /// Gets character by given name
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public RacerCharacter this[string name]
        {
            get
            {
                foreach (RacerCharacter racer in racers)
                    if (racer.Name == name)
                        return racer;

                foreach (RacerCharacter racer in finishRacers)
                    if (racer.Name == name)
                        return racer;

                throw new RacerNotFoundException();
            }
        }

        /// <summary>
        /// Gets standing of the racer
        /// </summary>
        /// <param name="racer"></param>
        /// <param name="sort"></param>
        /// <returns></returns>
        public int GetStandingOf(RacerCharacter racer, bool sort = true)
        {
            if (sort)
                racers.Sort();

            for (int i = 0; i < racers.Count; ++i)
                if (racers[i].Equals(racer))
                    return (i + 1);

            throw new RacerNotFoundException();
        }

        /// <summary>
        /// Calculates current lap of the racer
        /// </summary>
        /// <param name="racer"></param>
        /// <returns></returns>
        int getLap(RacerCharacter racer)
        {
            return (int)(racer.TotalTrackLength / racer.RaceTrack.TrackLength / 60) + 1;
        }

        /// <summary>
        /// Determines whether racer finished the race
        /// </summary>
        /// <param name="racerCharacter"></param>
        /// <returns></returns>
        public bool HasFinishedRace(RacerCharacter racerCharacter)
        {
            return getLap(racerCharacter) == laps;
        }

        #region Properties

        /// <summary>
        /// Standing of human racer
        /// </summary>
        public int HumanRacerStanding
        {
            get
            {
                foreach (RacerCharacter racer in racers)
                    if (racer.IsHuman)
                        return GetStandingOf(racer);

                foreach (RacerCharacter racer in finishRacers)
                    if (racer.IsHuman)
                        return GetStandingOf(racer);

                throw new NoHumanRacerException();
            }
        }

        /// <summary>
        /// Human racer
        /// </summary>
        public RacerCharacter HumanRacer
        {
            get
            {
                foreach (RacerCharacter racer in racers)
                    if (racer.IsHuman)
                        return racer;

                foreach (RacerCharacter racer in finishRacers)
                    if (racer.IsHuman)
                        return racer;

                throw new NoHumanRacerException();
            }
        }

        /// <summary>
        /// Winner racer
        /// </summary>
        public RacerCharacter Winner
        {
            get
            {
                return finishRacers[0];
            }
        }

        /// <summary>
        /// Current lap of the human racer
        /// </summary>
        public int CurrentLap
        {
            get
            {
                return getLap(HumanRacer);
            }
        }

        /// <summary>
        /// Gets if race is finished
        /// </summary>
        public bool RaceFinished
        {
            get
            {
                if (freeRace)
                    return false;

                if (!raceFinished)
                    return raceFinished = HasFinishedRace(racers[0]);

                return true;
            }
        }

        /// <summary>
        /// Gets or sets if race is a "free race"
        /// </summary>
        public bool FreeRace
        {
            get { return freeRace; }
            set { freeRace = value; }
        }

        /// <summary>
        /// Gets or sets racers in controller
        /// </summary>
        public List<RacerCharacter> Racers
        {
            get { return racers; }
            set { racers = value; }
        }

        /// <summary>
        /// Gets or sets finished racers in controller
        /// </summary>
        public List<TimeCountedRacer> FinishRacers
        {
            get { return finishRacers; }
            set { finishRacers = value; }
        }

        /// <summary>
        /// Gets or sets number of laps
        /// </summary>
        public int Laps
        {
            get { return laps; }
            set { laps = value; }
        }

        #endregion

        #region Exception Classes

        /// <summary>
        /// Exception class representing the situation when there is no racer to be found which is unexpected
        /// </summary>
        public class RacerNotFoundException : Exception
        {
            public RacerNotFoundException()
                : base("Racer has not been found in racers!")
            {
            }
        }

        /// <summary>
        /// Exception class representing the situation when there is no human racer unexpectedly
        /// </summary>
        public class NoHumanRacerException : Exception
        {
            public NoHumanRacerException()
                : base("There must be a human racer in the race!")
            {
            }
        }

        #endregion

        #region Fields

        int laps;
        bool freeRace = false;
        bool raceFinished = false;
        List<RacerCharacter> racers = new List<RacerCharacter>();
        List<TimeCountedRacer> finishRacers = new List<TimeCountedRacer>();

        #endregion
    }
}
